package pl.edu.pw.polygen.modeler.client.object;

import java.util.ArrayList;
import java.util.List;

import org.vaadin.gwtgraphics.client.Line;
import org.vaadin.gwtgraphics.client.shape.Text;

import pl.edu.pw.polygen.modeler.client.graph.BaseModel;
import pl.edu.pw.polygen.modeler.client.graph.EdgeModel;
import pl.edu.pw.polygen.modeler.client.graph.EdgeType;
import pl.edu.pw.polygen.modeler.client.graph.GraphHandler;
import pl.edu.pw.polygen.modeler.client.graph.GraphModel;
import pl.edu.pw.polygen.modeler.client.graph.IPropertyChangeListener;
import pl.edu.pw.polygen.modeler.client.graph.VertexModel;
import pl.edu.pw.polygen.modeler.client.object.Vertex.VertexType;
import pl.edu.pw.polygen.modeler.client.object.listener.EmptyListener;
import pl.edu.pw.polygen.modeler.client.object.listener.IPolygenObjectListener;
import pl.edu.pw.polygen.modeler.client.object.listener.PolygenObjectListenerMultiton;
import pl.edu.pw.polygen.modeler.client.object.listener.SelectableSegmentListener;
import pl.edu.pw.polygen.modeler.client.utils.Scale;
import pl.edu.pw.polygen.modeler.client.workspace.IWorkspace;

import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseOverEvent;
import com.google.gwt.event.dom.client.MouseOverHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;

/**
 * @author borysfan
 * @version 0.0.1
 */
public class Segment extends Line implements IPolygenObject, IPropertyChangeListener{

	private VertexModel beginVertexModel;
	private VertexModel endVertexModel;
	private EdgeModel edgeModel;
	
	private Vertex beginVertex;	
	private Vertex endVertex;
	private Scale scale;
	private List<IPolygenObject> polygenParents;
	private Segment instance;
	private Text boundaryText;
	
	private IWorkspace context;
	private IPolygenObjectListener polygenObjectListener;
	private boolean selected = false;
	private boolean holded = false;
	
	/**
	 * x coordination of mouse position when it is clicked
	 */
	private int mouseClickedX;
	
	/**
	 * y coordination of mouse position when it is clicked
	 */
	private int mouseClickedY;
	
	public static String DEFAULT_COLOR = "black";
	public static String HOVER_COLOR = "green";
	public static String SELECTED_COLOR = "red";
	
	public static int DEFAULT_WIDTH = 1;
	public static int HOVER_WIDTH = 2;
	public static int SELECTED_WIDTH = 2;
	
	public Segment(int x1, int y1, int x2, int y2, IWorkspace context) {
		super(x1, y1, x2, y2);
		boundaryText = new Text((x1+x2)/2, (y1+y2)/2-5, "");
		boundaryText.setFontSize(10);
		boundaryText.setVisible(context.getBoundariesVisibility());
		polygenParents = new ArrayList<IPolygenObject>();
		scale = Scale.getInstance();
		instance = this;
		this.setStrokeColor(DEFAULT_COLOR);
		this.context = context;
		this.initializeModel();
		endVertex.hold();
	}
	
	public Segment(Vertex beginVertex, int x2, int y2, IWorkspace context){
		super(beginVertex.getX(), beginVertex.getY(), x2, y2);
		polygenParents = new ArrayList<IPolygenObject>();
		boundaryText = new Text((beginVertex.getX()+x2)/2, (beginVertex.getY()+y2)/2-5, "");
		boundaryText.setFontSize(10);
		boundaryText.setVisible(context.getBoundariesVisibility());
		scale = Scale.getInstance();
		instance = this;
		this.setStrokeColor(DEFAULT_COLOR);
		this.context = context;
		this.initializeModel(beginVertex);
		endVertex.hold();
	}
	
	public Segment(Vertex beginVertex, Vertex endVertex, int boundary, IWorkspace context){
		super(0,0,0,0);
		polygenParents = new ArrayList<IPolygenObject>();
		boundaryText = new Text((beginVertex.getX() + endVertex.getX())/2, (beginVertex.getY()+endVertex.getY())/2-5, "");
		boundaryText.setFontSize(10);
		boundaryText.setVisible(context.getBoundariesVisibility());
		scale = Scale.getInstance();
		instance = this;
		this.setStrokeColor(DEFAULT_COLOR);
		this.context = context;
		setBeginVertex(beginVertex);
		setEndVertex(endVertex);
		beginVertex.addPolygenParent(this);
		endVertex.addPolygenParent(this);
		beginVertexModel = (VertexModel)beginVertex.getModel();
		beginVertexModel.addPropertyChangeListener(this);
		endVertexModel = (VertexModel)endVertex.getModel();
		endVertexModel.addPropertyChangeListener(this);
		edgeModel = (EdgeModel) GraphHandler.getInstance(context.getId()).addEdge(beginVertexModel, endVertexModel, EdgeType.SEGMENT);
		edgeModel.setBoundary(boundary);
		//GraphHandler.getInstance(context.getId()).addEdge(endVertexModel, beginVertexModel, EdgeType.SEGMENT);
	}
	
	
	
	private void initializeModel(){
		beginVertex = new Vertex(getX1(), getY1(),context,VertexType.NORMAL_VERTEX);
		beginVertex.addPolygenParent(this);
		endVertex = new Vertex(getX2(), getY2(),context,VertexType.NORMAL_VERTEX);
		endVertex.addPolygenParent(this);
		beginVertexModel = (VertexModel)beginVertex.getModel();
		beginVertexModel.addPropertyChangeListener(this);
		endVertexModel = (VertexModel)endVertex.getModel();
		endVertexModel.addPropertyChangeListener(this);
		edgeModel = (EdgeModel) GraphHandler.getInstance(context.getId()).addEdge(beginVertexModel, endVertexModel, EdgeType.SEGMENT);
	}
	
	private void initializeModel(Vertex bv){
		beginVertex = bv;
		beginVertex.addPolygenParent(this);
		endVertex = new Vertex(getX2(), getY2(),context,VertexType.NORMAL_VERTEX);
		endVertex.addPolygenParent(this);
		beginVertexModel = (VertexModel)beginVertex.getModel();
		beginVertexModel.addPropertyChangeListener(this);
		endVertexModel = (VertexModel)endVertex.getModel();
		endVertexModel.addPropertyChangeListener(this);
		edgeModel = (EdgeModel) GraphHandler.getInstance(context.getId()).addEdge(beginVertexModel, endVertexModel, EdgeType.SEGMENT);
	}
	/**
	 * Prepare object's actions. Initialize ends of segment
	 */
	@Override
	public void initialize() {
		this.initListeners();
		beginVertex.initialize();
		endVertex.initialize();
		this.initStates();
	}
		
	private void initListeners(){
		setLocked();
		MouseActions mouseActions = new MouseActions();
		this.addMouseOutHandler(mouseActions);
		this.addMouseOverHandler(mouseActions);
		this.addMouseDownHandler(mouseActions);
		this.addMouseUpHandler(mouseActions);
	}
	
	private void initStates(){
		this.selected = false;
		this.holded = false;
	}
	
	@Override
	public void notifyChanges() {
		beginVertexModel =(VertexModel)beginVertex.getModel();
		endVertexModel = (VertexModel)endVertex.getModel();
		int x1 = scale.getViewXValue(beginVertexModel.getX());
		int y1 = scale.getViewYValue(beginVertexModel.getY());
		int x2 = scale.getViewXValue(endVertexModel.getX());
		int y2 = scale.getViewYValue(endVertexModel.getY());
		setX1(x1);
		setY1(y1);
		setX2(x2);
		setY2(y2);
		if(context.getBoundariesVisibility()){
			boundaryText.setVisible(true);
			boundaryText.setX((x1+x2)/2);
			boundaryText.setY((y1+y2)/2-5);
			if(edgeModel.getBoundary()!=0){
				boundaryText.setText("("+edgeModel.getBoundary()+")");
			}else{
				boundaryText.setText("");
			}
		}else{
			boundaryText.setVisible(false);
		}
	}
	
	/* draw method which is called when we make some actions on objec. ie: move,
	* change position of ends
	*/
	@Override
	public void draw() {
		if (isHolded()) {
			this.setPosition(context.getMouseX(), context.getMouseY());
			context.showProperties(this);
		}
	}

	/**
	 * adds Segment object to canvas 
	 */
	@Override
	public void add() {
		this.addToCanvas();
		context.add(this);
		beginVertex.add();
		endVertex.add();
	}

	@Override
	public void addToCanvas() {
		this.setParent(null);
		context.getCanvas().insert(this, 0);
		context.getCanvas().add(boundaryText);
	}
	
	/**
	 * removes Segment object from canvas 
	 */
	public void remove() {
		removeFromCanvas();
		context.remove(this);
	}
	
	@Override
	public void removeFromCanvas() {
		beginVertex.remove();
		endVertex.remove();
		context.getCanvas().remove(this);
	}

	@Override
	public void unselect() {
		context.removeFromSelected(this);
		setHolded(false);
		setSelected(false);
		setStrokeColor(DEFAULT_COLOR);
		setStrokeWidth(DEFAULT_WIDTH);
	}

	@Override
	public void drop() {
		beginVertex.drop();
		endVertex.drop();
		context.removeFromHolded(this);
		setHolded(false);
		setSelected(false);
		setStrokeColor(DEFAULT_COLOR);
		setStrokeWidth(DEFAULT_WIDTH);
	}
	
	
	@Override
	public void over() {
		setStrokeColor(HOVER_COLOR);
		setStrokeWidth(HOVER_WIDTH);
	}

	@Override
	public void out() {
		setStrokeColor(DEFAULT_COLOR);
		setStrokeWidth(DEFAULT_WIDTH);
	}
	
	@Override
	public void select() {
		selected = true;
		holded = false;
		setStrokeColor(SELECTED_COLOR);
		setStrokeWidth(SELECTED_WIDTH);
		context.addToSelected(this);
	}

	@Override
	public void hold() {
		selected = false;
		holded = true;
		setStrokeColor(HOVER_COLOR);
		setStrokeWidth(HOVER_WIDTH);
		mouseClickedX = context.getMouseX();
		mouseClickedY = context.getMouseY();
		context.addToHolded(this);
	}

	@Override
	public void merge(IPolygenObject sourceObject, IPolygenObject targetObject) {
		if((sourceObject instanceof Vertex) && (targetObject instanceof Vertex)){
			GraphModel graph = GraphHandler.getInstance(context.getId());
			graph.removeEdge(beginVertexModel,endVertexModel,EdgeType.SEGMENT);
			//graph.removeEdge(endVertexModel,beginVertexModel,EdgeType.SEGMENT);
			Vertex sourceVertex = (Vertex) sourceObject;
			Vertex targetVertex = (Vertex) targetObject;
			if(beginVertex==sourceVertex){
				beginVertex = targetVertex;
			}else if(endVertex == sourceVertex){
				endVertex = targetVertex;
			}
			beginVertexModel = (VertexModel)beginVertex.getModel();
			endVertexModel = (VertexModel)endVertex.getModel();
			graph.addEdge(beginVertexModel, endVertexModel, EdgeType.SEGMENT);
			//graph.addEdge(endVertexModel, beginVertexModel, EdgeType.SEGMENT);
		}
	}
	
	@Override
	public void setPolygenObjectListener(IPolygenObjectListener polygenObjectListener) {
		this.polygenObjectListener = polygenObjectListener;
	}

	@Override
	public void setSelectable() {
		this.setPolygenObjectListener(PolygenObjectListenerMultiton.getInstance(SelectableSegmentListener.class));
	}

	@Override
	public void setMergeable() {
		this.setPolygenObjectListener(PolygenObjectListenerMultiton.getInstance(EmptyListener.class));
	}
	
	@Override
	public void setLocked() {
		this.setPolygenObjectListener(PolygenObjectListenerMultiton.getInstance(EmptyListener.class));
	}

	@Override
	public IWorkspace getContext() {
		return context;
	}
	
	/**
	 * @param context
	 * set context to all objects in segment
	 */
	public void setContext(IWorkspace context) {
		this.context = context;
		this.beginVertex.setContext(context);
		this.endVertex.setContext(context);
	}
	

	@Override
	public void setPosition(int x, int y) {
		int dx = x - mouseClickedX;
		int dy = y - mouseClickedY;
		mouseClickedX = x;
		mouseClickedY = y;

		beginVertex.setPosition(this.beginVertex.getX() + dx, this.beginVertex.getY() + dy);
		endVertex.setPosition(this.endVertex.getX() + dx, this.endVertex.getY() + dy);
	}
	

	@Override
	public void setModel(BaseModel model) {
		if(model instanceof EdgeModel){
			EdgeModel edgeModel = (EdgeModel)model;
			this.beginVertexModel = (VertexModel)edgeModel.getBegin();
			this.endVertexModel = (VertexModel)edgeModel.getEnd();
		}
	}

	@Override
	public BaseModel getModel() {
		return (BaseModel)beginVertexModel.getEdge(endVertexModel);
	}
	
	@Override
	public List<IPolygenObject> getPolygenParents() {
		return polygenParents;
	}

	@Override
	public void addPolygenParent(IPolygenObject polygenParent) {
		if(!polygenParents.contains(polygenParent))
			this.polygenParents.add(polygenParent);
	}
	
	@Override
	public void removePolygenParent(IPolygenObject polygenParent) {
		if(polygenParents.contains(polygenParent))
			this.polygenParents.remove(polygenParent);
	}
	

	@Override
	public void showBoundary() {
		this.boundaryText.setVisible(true);
	}

	@Override
	public void hideBoundary() {
		this.boundaryText.setVisible(false);
	}

	public boolean isSelected() {
		return selected;
	}

	public void setSelected(boolean selected) {
		this.selected = selected;
	}
	
	public boolean isHolded() {
		return holded;
	}

	public void setHolded(boolean holded) {
		this.holded = holded;
	}

	/**
	 * @return Vertex
	 * begin of Segment
	 */
	public Vertex getBeginVertex() {
		return beginVertex;
	}

	/**
	 * @param beginVertex
	 * set begin of segment. Called when we want to merge polygen objects
	 */
	public void setBeginVertex(Vertex beginVertex) {
		if(this.beginVertex!=null)
			this.beginVertex.remove();
		this.beginVertex = beginVertex;
		this.setX1(beginVertex.getX());
		this.setY1(beginVertex.getY());
	}

	/** 
	 * @return Vertex
	 * end of Segment
	 */
	public Vertex getEndVertex() {
		return endVertex;
	}

	/**
	 * @param endVertex
	 * set end of segment. Called when we want to merge polygen objects
	 */
	public void setEndVertex(Vertex endVertex) {
		if(this.endVertex!=null)
			this.endVertex.remove();
		this.endVertex = endVertex;
		this.setX2(endVertex.getX());
		this.setY2(endVertex.getY());
	}

	/**
	 * @param x
	 * @param y
	 * Compute and set coordinates of Segment after move of vector [x,y]
	 */
	private void moveTo(int x, int y) {
		int dx = x - mouseClickedX;
		int dy = y - mouseClickedY;
		mouseClickedX = x;
		mouseClickedY = y;

		this.setX1(getX1() + dx);
		this.setY1(getY1() + dy);
		this.setX2(getX2() + dx);
		this.setY2(getY2() + dy);

		
		this.beginVertex.setX(this.beginVertex.getX() + dx);
		this.beginVertex.setY(this.beginVertex.getY() + dy);

		this.endVertex.setX(this.endVertex.getX() + dx);
		this.endVertex.setY(this.endVertex.getY() + dy);
	}

	/**
	 * @author borysfan
	 * Implementation of mouse actions 
	 */
	private class MouseActions implements MouseOutHandler, MouseOverHandler,
			MouseDownHandler, MouseUpHandler {

		@Override
		public void onMouseDown(MouseDownEvent event) {
			polygenObjectListener.onMouseDown(event, instance);
		}

		@Override
		public void onMouseUp(MouseUpEvent event) {
			polygenObjectListener.onMouseUp(event, instance);
		}

		@Override
		public void onMouseOver(MouseOverEvent event) {
			polygenObjectListener.onMouseOver(event, instance);
		}

		@Override
		public void onMouseOut(MouseOutEvent event) {
			polygenObjectListener.onMouseOut(event, instance);
		}
		
	}

}
